package cn.geminis.workflow.service.service.engine.expression;

import cn.geminis.workflow.service.controller.vo.Process;
import cn.geminis.workflow.service.service.model.RemoteDataService;
import cn.geminis.workflow.service.utils.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.ProcessEngine;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author puddi
 */
@Service
@Scope("request")
@RequiredArgsConstructor
public class ExpressionValueContext {

    private static final String PROCESS_KEY = "process";
    private static final String TASK_KEY = "task";
    private static final String DATA_KEY = "data";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static Map<String, Object> obj2Map(Object value) {
        return OBJECT_MAPPER.convertValue(value, Map.class);
    }

    private String processInstanceId;
    private String taskInstanceId;
    private final List<String> loadedRemoteData = new ArrayList<>();

    @Getter
    private final Map<String, Object> variables = new HashMap<>();
    private final ProcessEngine engine;
    private final RemoteDataService remoteDataService;

    public ExpressionValueContext clear() {
        this.variables.clear();
        this.loadedRemoteData.clear();
        return this;
    }

    public ExpressionValueContext withProcess(Object source) {
        var process = ObjectUtils.convert(source, Process.class);
        this.variables.put(PROCESS_KEY, obj2Map(process));
        return this;
    }

    public ExpressionValueContext withProcess(String processInstanceId) {
        if (StringUtils.hasText(processInstanceId) && !processInstanceId.equals(this.processInstanceId)) {
            this.processInstanceId = processInstanceId;
            var instance = this.engine.getHistoryService().createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();

            this.withProcess(instance);
        }

        return this;
    }

    public ExpressionValueContext withTask(Object source) {
        var task = ObjectUtils.convert(source, Process.class);
        this.variables.put(TASK_KEY, obj2Map(task));
        return this;
    }

    public ExpressionValueContext withTask(String taskInstanceId) {
        if (StringUtils.hasText(taskInstanceId) && !taskInstanceId.equals(this.taskInstanceId)) {
            this.taskInstanceId = taskInstanceId;
            var instance = this.engine.getHistoryService().createHistoricTaskInstanceQuery()
                    .taskId(taskInstanceId)
                    .singleResult();

            this.withTask(instance);
        }
        return this;
    }

    public ExpressionValueContext withData(String url) {
        if (!this.loadedRemoteData.contains(url)) {
            var data = this.remoteDataService.get(url, Map.class);
            withData(data);
            this.loadedRemoteData.add(url);
        }
        return this;
    }

    public ExpressionValueContext withData(Map<String, Object> value) {
        if (!org.springframework.util.ObjectUtils.isEmpty(value)) {
            var target = (Map<String, Object>) this.variables.get(DATA_KEY);
            if (org.springframework.util.ObjectUtils.isEmpty(target)) {
                this.variables.put(DATA_KEY, value);
            } else {
                target.putAll(value);
            }
        }
        return this;
    }

}
